package it.uniroma2.art.coda.provisioning;

import java.io.File;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import org.apache.felix.bundlerepository.Capability;
import org.apache.felix.bundlerepository.Requirement;
import org.apache.felix.bundlerepository.Resolver;
import org.apache.felix.bundlerepository.Resource;
import org.apache.felix.bundlerepository.impl.LocalResourceImpl;
import org.apache.felix.bundlerepository.impl.RepositoryAdminImpl;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import it.uniroma2.art.owlart.io.RDFFormat;
import it.uniroma2.art.owlart.model.NodeFilters;
import it.uniroma2.art.owlart.models.OWLArtModelFactory;
import it.uniroma2.art.owlart.models.OWLModel;
import it.uniroma2.art.owlart.navigation.ARTNodeIterator;
import it.uniroma2.art.owlart.sesame2impl.factory.ARTModelFactorySesame2Impl;
import it.uniroma2.art.owlart.sesame2impl.models.conf.Sesame2ModelConfiguration;
import it.uniroma2.art.owlart.sesame2impl.models.conf.Sesame2NonPersistentInMemoryModelConfiguration;

/**
 * // It is necessary to put in the components directory the following //
 * artifact. // <artifact> // <groupId>org.apache.felix</groupId> //
 * <artifactId>org.apache.felix.bundlerepository</artifactId> //
 * <version>1.6.6</version> // </artifact>
 * 
 * // Download URL: //
 * http://search.maven.org/remotecontent?filepath=org/apache/
 * felix/org.apache.felix
 * .bundlerepository/1.6.6/org.apache.felix.bundlerepository-1.6.6.jar
 * 
 * @author manuel
 * 
 */
public class ComponentProvider {
	private static final Logger logger = LoggerFactory.getLogger(ComponentProvider.class);

	private static final String REPOSITORY_ADMIN_SERVICE = "org.apache.felix.bundlerepository.RepositoryAdmin";
	private static final String REQUIREMENT_INTERFACE_NAME = "org.apache.felix.bundlerepository.Requirement";

	private BundleContext hostBundleContext;
	private RepositoryAdminImpl repositoryAdminImpl;

	private Map<String, ServiceReference> serviceReferencesMap;
	private Map<String, Object> serviceObjectsMap;

	private File tempDir;

	public ComponentProvider(BundleContext hostBundleContext, File tempDir) throws Exception {
		this.hostBundleContext = hostBundleContext;
		this.repositoryAdminImpl = new RepositoryAdminImpl(hostBundleContext, null);
		this.tempDir = tempDir;

		this.serviceReferencesMap = new HashMap<String, ServiceReference>();
		this.serviceObjectsMap = new HashMap<String, Object>();
	}

	public void dispose() {
		for (ServiceReference sr : serviceReferencesMap.values()) {
			this.hostBundleContext.ungetService(sr);
		}

		this.repositoryAdminImpl.dispose();
	}

	public Object lookup(String contract) throws Exception {
		Object service = serviceObjectsMap.get(contract);

		if (service != null) {
			return service;
		}
		
		// TODO: since Felix does not retain service properties in the Local OBR, we have to iterate
		// over all local resources to find a suitable service (since we don't know the service name)
		Resource[] resources = repositoryAdminImpl.getLocalRepository().getResources();
		for (Resource r : resources) {
			LocalResourceImpl localResource = (LocalResourceImpl)r;
			ServiceReference[] refs = localResource.getBundle().getRegisteredServices();
			for (ServiceReference sr : refs) {
				Object contractObj = sr.getProperty("it.uniroma2.art.coda.contract");
				if (contract.equals(contractObj)) {
					service = hostBundleContext.getService(sr);
					serviceReferencesMap.put(contract, sr);
					serviceObjectsMap.put(contract, service);
					return service;
				}
			}
		}
		
		Requirement req = repositoryAdminImpl.getHelper().requirement("service",
				String.format("(it.uniroma2.art.coda.contract=%s)", contract));
		Resource[] candidateResources = repositoryAdminImpl.discoverResources(new Requirement[] { req });

		
		if (candidateResources.length == 0) {
			discover(contract);

			candidateResources = repositoryAdminImpl.discoverResources(new Requirement[] { req });
			if (candidateResources.length == 0) {
				throw new Exception("No candidate resource for contract: " + contract);
			}
		}

		int chosen = 0;

		Resource chosenResource = candidateResources[0];

		Resolver resolver = repositoryAdminImpl.resolver();
		resolver.add(chosenResource);

		boolean success = resolver.resolve();

		if (!success)
			throw new Exception("Cannot resolve exception while resolving resource: " + chosenResource);

		resolver.deploy(Resolver.START);

		Capability[] capabilities = chosenResource.getCapabilities();

		for (int i = 0; i < capabilities.length; i++) {
			Capability cap = capabilities[i];

			if (cap.getName().equals("service")
					&& contract.equals(cap.getPropertiesAsMap().get("it.uniroma2.art.coda.contract"))) {
				String serviceName = (String) cap.getPropertiesAsMap().get("service");

				ServiceReference[] refs = hostBundleContext.getServiceReferences(serviceName,
						String.format("(it.uniroma2.art.coda.contract=%s)", contract));

				service = hostBundleContext.getService(refs[0]);
				serviceReferencesMap.put(contract, refs[0]);
				serviceObjectsMap.put(contract, service);

				return service;
			}
		}

		throw new Exception("Cannot get the service");
	}

	private void discover(String contract) throws Exception {
		logger.info("discover({})", contract);
		ARTModelFactorySesame2Impl factImpl = new ARTModelFactorySesame2Impl();
		OWLArtModelFactory<Sesame2ModelConfiguration> fact = OWLArtModelFactory.createModelFactory(factImpl);
		Sesame2NonPersistentInMemoryModelConfiguration conf = fact
				.createModelConfigurationObject(Sesame2NonPersistentInMemoryModelConfiguration.class);

		OWLModel model = fact.loadOWLModel(null, tempDir.getAbsolutePath());
		try {
			model.addRDF(new URL(contract), null, RDFFormat.TURTLE, NodeFilters.MAINGRAPH);
			ARTNodeIterator it = model.listValuesOfSubjPredPair(model.createURIResource(contract),
					model.createURIResource("http://art.uniroma2.it/coda/ns/authoritativeOBR"), true);

			String repository = null;
			if (it.streamOpen()) {
				try {
					repository = it.getNext().asURIResource().getURI();
					repositoryAdminImpl.addRepository(new URL(repository));

					logger.info("Added repository: {}", repository);
				} finally {
					it.close();
				}
			}
		} finally {
			model.close();
		}
	}

	// public static void main(String[] args) throws Exception {
	// CODAOSGiManger codaOSGiManger = CODAOSGiManger.initialize(new
	// File("/home/manuel/coda-dirs/cache"));
	// codaOSGiManger.loadComponents(new
	// File("/home/manuel/coda-dirs/components"), Layout.Flat);
	// BundleContext hostContext =
	// codaOSGiManger.getFramework().getBundleContext();
	// ComponentProvider cp = new ComponentProvider(hostContext);
	// // cp.discover("file:///home/manuel/default");
	// Object o = cp.lookup("file:///home/manuel/default");
	// System.err.println(o);
	//
	// Object value = "hello";
	//
	// Set<Method> methods = ReflectionUtils.getMethods(o.getClass(),
	// ReflectionUtils.withParametersAssignableTo(String.class,
	// value.getClass()));
	// Method m = methods.iterator().next();
	// Object e = m.invoke(o, "http://goo#", value);
	//
	// System.err.println("###" + e);
	//
	// cp.dispose();
	// }
}
